package taskhandler

import (
	"encoding/xml"
	"fmt"
	"io/ioutil"
	"os"
	"sort"
	"strings"
	"time"

	"cvevulner/common"
	"cvevulner/models"

	"github.com/360EntSecGroup-Skylar/excelize/v2"
	"github.com/astaxie/beego/logs"
)

type Updates struct {
	XMLName xml.Name `xml:"updates,omitempty"`
	Updatex []Update `xml:"update,omitempty"`
}

type Update struct {
	XMLName     xml.Name    `xml:"update,omitempty"`
	From        string      `xml:"from,attr"`
	Type        string      `xml:"type,attr"`
	Status      string      `xml:"status,attr"`
	Id          string      `xml:"id"`
	Title       string      `xml:"title"`
	Severity    string      `xml:"severity"`
	Release     string      `xml:"release"`
	Issued      *Issued     `xml:"issued,omitempty"`
	References  *References `xml:"references,omitempty"`
	Description string      `xml:"description"`
	Pkglist     *Pkglist    `xml:"pkglist,omitempty"`
}

type Issued struct {
	XMLName xml.Name `xml:"issued,omitempty"`
	Date    string   `xml:"date,attr"`
}

type References struct {
	XMLName   xml.Name    `xml:"references,omitempty"`
	Reference []Reference `xml:"reference,omitempty"`
}

type Reference struct {
	XMLName xml.Name `xml:"reference,omitempty"`
	Href    string   `xml:"href,attr"`
	Id      string   `xml:"id,attr"`
	Title   string   `xml:"title,attr"`
	Type    string   `xml:"type,attr"`
}

type Pkglist struct {
	XMLName    xml.Name    `xml:"pkglist,omitempty"`
	Collection *Collection `xml:"collection,omitempty"`
}

type Collection struct {
	XMLName xml.Name  `xml:"collection,omitempty"`
	Name    string    `xml:"name"`
	Package []Package `xml:"package,omitempty"`
}

type Package struct {
	XMLName  xml.Name `xml:"package,omitempty"`
	Arch     string   `xml:"arch,attr"`
	Name     string   `xml:"name,attr"`
	Release  string   `xml:"release,attr"`
	Version  string   `xml:"version,attr"`
	Filename string   `xml:"filename"`
}

// Intermediate information
type SecurityNoticeXml struct {
	CveNum         string
	Title          string
	Type           string
	ReferenceLink  string
	Description    string
	openEulerScore float64
}

type PackRpm struct {
	PackName string
}

type CveXml struct {
	PublicDate     string
	OpenEulerSANum string
	Introduction   string
	CveLevel       string
	OwnedComponent string
	SecurityNotice map[string][]SecurityNoticeXml
	PackRpmx       map[string][]PackRpm
}

func ReadXml(filePath string, dpdates *Updates) error {
	if filePath == "" || len(filePath) == 0 {
		filePath = "download/updateinfo.xml"
	}
	fisExist, ferr := PathExists(filePath)
	if !fisExist {
		logs.Error(ferr)
		return ferr
	}
	fd, err := os.Open(filePath)
	if err != nil {
		logs.Error("open file err : ", err, ",filePath: ", filePath)
		return err
	}
	defer fd.Close()
	fileContent, err := ioutil.ReadAll(fd)
	if err != nil {
		logs.Error("read file err : ", err, ", filePath: ", filePath)
		return err
	}
	err = xml.Unmarshal(fileContent, dpdates)
	if err != nil {
		logs.Error("unmarshal err : ", err, ", fileContent: ", fileContent)
		return err
	}
	return nil
}

func AbiAffectedVersionBool(abiVersion, branch string) bool {
	abiVersionList := []string{}
	brandsGroup := strings.Split(abiVersion, ",")
	if len(brandsGroup) > 0 {
		for _, brand := range brandsGroup {
			if brand == "" || len(brand) < 2 {
				continue
			}
			brand = common.BranchVersionRep(brand)
			brandList := strings.Split(brand, ":")
			if len(brandList) > 1 {
				prams := strings.Replace(brandList[1], " ", "", -1)
				prams0 := strings.Replace(brandList[0], " ", "", -1)
				if prams == "是" && prams0 == branch {
					abiVersionList = append(abiVersionList, brandList[0])
					break
				}
			} else {
				brandList = strings.Split(brand, "：")
				if len(brandList) > 1 {
					prams := strings.Replace(brandList[1], " ", "", -1)
					prams0 := strings.Replace(brandList[0], " ", "", -1)
					if prams == "是" && prams0 == branch {
						abiVersionList = append(abiVersionList, brandList[0])
						break
					}
				}
			}
		}
	}
	if len(abiVersionList) > 0 {
		return true
	}
	return false
}

func CreateUpdateExcel(excelPath string) string {
	// File storage directory
	sheetName := "cve_list"
	xlsx := excelize.NewFile()
	index := xlsx.NewSheet(sheetName)
	sheetTileMap := make(map[string]string)
	sheetTileMap["A1"] = "cve编号"
	sheetTileMap["B1"] = "issue编号"
	sheetTileMap["C1"] = "issue所属仓库"
	sheetTileMap["D1"] = "score"
	sheetTileMap["E1"] = "version"
	sheetTileMap["F1"] = "abi是否变化"
	for k, v := range sheetTileMap {
		xlsx.SetCellValue(sheetName, k, v)
	}
	xlsx.SetActiveSheet(index)
	err := xlsx.SaveAs(excelPath)
	if err != nil {
		logs.Error(err)
		return ""
	}
	return excelPath
}

func CreateUpdateData(its models.IssueTemplate, affectBranch string) []interface{} {
	ownedVersion := ""
	cveData := make([]interface{}, 0)
	cveData = append(cveData, its.CveNum)
	cveData = append(cveData, its.IssueNum)
	cveData = append(cveData, its.OwnedComponent)
	cveData = append(cveData, its.OpenEulerScore)
	ownedVersion = its.OwnedVersion
	ori := models.QueryEulerOriginBRepo(its.OwnedComponent, affectBranch)
	if len(ori) > 0 {
		for _, or := range ori {
			if len(or.Branchs) > 0 {
				brSlice := strings.Split(or.Branchs, ",")
				if len(brSlice) > 0 {
					for _, br := range brSlice {
						if br == affectBranch {
							if len(or.Version) > 0 && len(or.Version) < 32 {
								ownedVersion = or.Version
								goto toVersion
							}
						}
					}
				}
			}
		}
	}
toVersion:
	cveData = append(cveData, ownedVersion)
	abiBool := AbiAffectedVersionBool(its.AbiVersion, affectBranch)
	if abiBool {
		cveData = append(cveData, "是")
	} else {
		cveData = append(cveData, "否")
	}
	return cveData
}

func ReadWriteUpdateExcel(excelPath, ownedComponent, cveNum, affectBranch string) error {
	sheetName := "cve_list"
	file, openErr := excelize.OpenFile(excelPath)
	if openErr != nil {
		logs.Error("fail to open the file, excelPath: ", excelPath, ", openErr: ", openErr)
		return openErr
	}
	vc := models.QueryCveByPackName(cveNum, ownedComponent, 1)
	if len(vc) > 0 {
		for _, v := range vc {
			issueTmp, err := models.GetIssueTemplateByStatuss(v.CveId)
			if err == nil {
				cveData := CreateUpdateData(issueTmp, affectBranch)
				if len(cveData) > 0 {
					rows, sheetErr := file.GetRows(sheetName)
					if sheetErr != nil {
						logs.Error(sheetErr)
					}
					idx := len(rows) + 1
					axis := fmt.Sprintf("A%d", idx)
					setErr := file.SetSheetRow(sheetName, axis, &cveData)
					if setErr != nil {
						logs.Error("setErr: ", setErr)
					}
				}
			}
		}
	}
	fileErr := file.SaveAs(excelPath)
	if fileErr != nil {
		logs.Error("Failed to save file, ", fileErr)
	}
	return fileErr
}

func WriteXml(filePath, excelName, affectBranch string, cveXmlList []CveXml, dpdates *Updates,
	securityNotice map[string][]SecurityNoticeXml, packRpmx map[string][]PackRpm) {
	for _, cveXml := range cveXmlList {
		upDatex := Update{From: "openeuler.org", Type: "security", Status: "stable", Id: cveXml.OpenEulerSANum,
			Title: cveXml.Introduction, Severity: cveXml.CveLevel, Release: "openEuler"}
		var pubDate Issued
		pubDate.Date = cveXml.PublicDate
		upDatex.Issued = &pubDate
		openEulerSANum := ""
		var rfs References
		if sn, ok := securityNotice[cveXml.OwnedComponent]; ok {
			if len(sn) > 0 {
				descriptionsList := make([]string, 0)
				openEulerScoreSlice := make([]float64, 0)
				for _, sec := range sn {
					var rf Reference
					rf.Id = sec.CveNum
					rf.Title = sec.CveNum
					rf.Href = sec.ReferenceLink
					rf.Type = "cve"
					descriptionsList = append(descriptionsList, sec.Description)
					openEulerScoreSlice = append(openEulerScoreSlice, sec.openEulerScore)
					rfs.Reference = append(rfs.Reference, rf)
					if len(openEulerSANum) < 2 {
						cfc := models.GetCvrfSaRecordByCve(sec.CveNum, affectBranch)
						if len(cfc) > 0 {
							for _, cf := range cfc {
								if len(cf.SortOpenEulerSANum) > 1 {
									openEulerSANum = cf.SortOpenEulerSANum
									break
								}
							}
						}
					}
					// write excel
					ReadWriteUpdateExcel(excelName, cveXml.OwnedComponent, sec.CveNum, affectBranch)
				}
				upDatex.Description = strings.Join(descriptionsList, " ")
				if len(openEulerScoreSlice) > 1 {
					sort.Float64s(openEulerScoreSlice)
				}
				cveLevel := models.OpenEulerScoreProc(openEulerScoreSlice[len(openEulerScoreSlice)-1])
				if strings.ToLower(cveLevel) == "low" {
					upDatex.Severity = "Low"
				} else if strings.ToLower(cveLevel) == "medium" {
					upDatex.Severity = "Moderate"
				} else if strings.ToLower(cveLevel) == "high" {
					upDatex.Severity = "Important"
				} else {
					upDatex.Severity = "Critical"
				}
			}
		}
		if len(openEulerSANum) > 2 {
			upDatex.Id = openEulerSANum
		}
		upDatex.References = &rfs
		var pl Pkglist
		var ct Collection
		ct.Name = "openEuler"
		if prx, ok := packRpmx[cveXml.OwnedComponent]; ok {
			if len(prx) > 0 {
				for _, pr := range prx {
					if strings.Contains(pr.PackName, ".src.") {
						continue
					}
					var pe Package
					pe.Filename = pr.PackName
					//pe.Name = cveXml.OwnedComponent
					if len(pr.PackName) > len(cveXml.OwnedComponent) {
						//packVersion := pr.PackName[len(cveXml.OwnedComponent) + 1: len(pr.PackName) - 4]
						packVersionList := strings.Split(pr.PackName, "-")
						if len(packVersionList) >= 3 {
							pe.Version = packVersionList[len(packVersionList)-2]
							rpmName := packVersionList[len(packVersionList)-1][:len(packVersionList[len(packVersionList)-1])-4]
							lastIndex := strings.LastIndexAny(rpmName, ".")
							if lastIndex != -1 {
								pe.Release = rpmName[:lastIndex]
								pe.Arch = rpmName[lastIndex+1:]
							}
							pe.Name = strings.Join(packVersionList[0:len(packVersionList)-2], "-")
						}
					}
					ct.Package = append(ct.Package, pe)
				}
			}
		}
		pl.Collection = &ct
		upDatex.Pkglist = &pl
		dpdates.Updatex = append(dpdates.Updatex, upDatex)
	}
	xmlOutPut, outPutErr := xml.MarshalIndent(dpdates, "", "	")
	if outPutErr == nil {
		headerBytes := []byte(xml.Header)
		xmlOutPutData := append(headerBytes, xmlOutPut...)
		os.Remove(filePath)
		ioutil.WriteFile(filePath, xmlOutPutData, os.ModePerm)
	} else {
		logs.Error(outPutErr)
	}
}

func BuildXml(cveXml *[]CveXml, v *models.ExcelExport,
	securityNotice map[string][]SecurityNoticeXml,
	packRpmx map[string][]PackRpm, affectBranch string) {
	pkg, pkgErr := models.GetCvePackageList(v.SecID, affectBranch)
	if pkgErr != nil {
		logs.Error(pkgErr)
		return
	}
	//cvex.SecurityNotice = make(map[string][]SecurityNoticeXml)
	sn := SecurityNoticeXml{CveNum: v.CveNum, Title: v.CveNum, Type: "cve", ReferenceLink: v.ReferenceLink,
		Description: deleteTailBlank(v.CveBrief) + "(" + v.CveNum + ")", openEulerScore: v.OpenEulerScore}
	if vx, ok := securityNotice[v.OwnedComponent]; !ok {
		snArry := []SecurityNoticeXml{}
		snArry = append(snArry, sn)
		securityNotice[v.OwnedComponent] = snArry
		var cvex CveXml
		if v.PublicDate == "" {
			v.PublicDate = time.Now().Format("2006-01-02")
		}
		cvex.OpenEulerSANum = v.OpenEulerSANum
		cvex.PublicDate = v.PublicDate
		if strings.ToLower(v.CveLevel) == "low" {
			cvex.CveLevel = "Low"
		} else if strings.ToLower(v.CveLevel) == "medium" {
			cvex.CveLevel = "Moderate"
		} else if strings.ToLower(v.CveLevel) == "high" {
			cvex.CveLevel = "Important"
		} else {
			cvex.CveLevel = "Critical"
		}
		if len(v.Introduction) > 1 {
			cvex.Introduction = v.Introduction[:len(v.Introduction)-1]
		} else {
			cvex.Introduction = v.Introduction
		}
		cvex.OwnedComponent = v.OwnedComponent
		*cveXml = append(*cveXml, cvex)
	} else {
		sameFlag := false
		for _, vu := range vx {
			if sn.CveNum == vu.CveNum {
				sameFlag = true
				break
			}
		}
		if !sameFlag {
			vx = append(vx, sn)
			securityNotice[v.OwnedComponent] = vx
		}
	}
	//cvex.PackRpmx = make(map[string][]PackRpm)
	if px, ok := packRpmx[v.OwnedComponent]; !ok {
		prArry := []PackRpm{}
		for _, v := range pkg {
			pr := PackRpm{PackName: v.PackName}
			prArry = append(prArry, pr)
		}
		packRpmx[v.OwnedComponent] = prArry
	} else {
		for _, v := range pkg {
			pnFlag := false
			for _, p := range px {
				if p.PackName == v.PackName {
					pnFlag = true
					break
				}
			}
			if !pnFlag {
				pr := PackRpm{PackName: v.PackName}
				px = append(px, pr)
			}
		}
		packRpmx[v.OwnedComponent] = px
	}
	return
}
